package userInterface.Panels;

import dataAccess.*;
import logging.*;
import settings.*;

import java.awt.datatransfer.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
import javax.swing.event.*;

/**
 * UI JPanel for editing Workflow objects.
 * @author BDS
 */
public class PanelWorkflows extends PanelBase
{
	private static final long serialVersionUID = 1L;

	private Environment currentEnvironment;
	private Workflow currentWorkflow;
	private Workflow copiedWorkflow;
	private int copyNumber;

	private ActionListener workflowActionListener;
	private ListSelectionListener workflowListSelectionListener;
	private TransferHandler workflowTransferHandler;
	private FocusAdapter workflowFocusAdapter;
	private KeyAdapter workflowKeyAdapter;
	private ChangeListener workflowChangeListener;
	
	private SpringLayout slWorkflowLayout;
	
	private JLabel lblEnvironment;
	private DefaultComboBoxModel<Environment> environments;
	private JComboBox<Environment> cbxEnvironments;
	
	private JButton btnAdd;
	private JButton btnRemove;
	private JButton btnCopy;
	private JButton btnPaste;
	private DefaultListModel<Workflow> workflows;
	private JList<Workflow> lstWorkflows;
	private JScrollPane scpWorkflows;
	
	private JLabel lblName;
	private JLabel lblIterations;
	private JLabel lblDescription;
	private JFormattedTextField txtName;
	private JSpinner sprIterations;
	private JTextPane txtDescription;
	private JButton btnSave;
	private JButton btnRevert;
	
	/**
	 * Class constructor.
	 */
	public PanelWorkflows()
	{
		workflowActionListener = new WorkflowActionListener();
		workflowListSelectionListener = new WorkflowListSelectionListener();
		workflowTransferHandler = new WorkflowTransferHandler();
		workflowFocusAdapter = new WorkflowFocusAdapter();
		workflowKeyAdapter = new WorkflowKeyAdapter();
		workflowChangeListener = new WorkflowChangeListener();
		
		slWorkflowLayout = new SpringLayout();
		setLayout(slWorkflowLayout);
		
		lblEnvironment = new JLabel("Environment:");
		slWorkflowLayout.putConstraint(SpringLayout.WEST, lblEnvironment, 10, SpringLayout.WEST, this);
		add(lblEnvironment);
		
		environments = new DefaultComboBoxModel<Environment>();
		cbxEnvironments = new JComboBox<Environment>(environments);
		cbxEnvironments.setActionCommand("Environment changed");
		cbxEnvironments.addActionListener(workflowActionListener);		
		slWorkflowLayout.putConstraint(SpringLayout.VERTICAL_CENTER, lblEnvironment, 0, SpringLayout.VERTICAL_CENTER, cbxEnvironments);
		slWorkflowLayout.putConstraint(SpringLayout.WEST, cbxEnvironments, 6, SpringLayout.EAST, lblEnvironment);
		slWorkflowLayout.putConstraint(SpringLayout.NORTH, cbxEnvironments, 10, SpringLayout.NORTH, this);
		slWorkflowLayout.putConstraint(SpringLayout.EAST, cbxEnvironments, -10, SpringLayout.EAST, this);
		add(cbxEnvironments);
		
		btnAdd = new JButton();
		btnAdd.setIcon(new ImageIcon(getClass().getResource("/images/add.png")));
		btnAdd.setToolTipText("Add a new workflow");
		btnAdd.setEnabled(false);
		btnAdd.setMnemonic(KeyEvent.VK_N);
		btnAdd.setActionCommand("Add");
		btnAdd.addActionListener(workflowActionListener);
		slWorkflowLayout.putConstraint(SpringLayout.WEST, btnAdd, 10, SpringLayout.WEST, this);
		slWorkflowLayout.putConstraint(SpringLayout.NORTH, btnAdd, 6, SpringLayout.SOUTH, cbxEnvironments);
		slWorkflowLayout.putConstraint(SpringLayout.EAST, btnAdd, 50, SpringLayout.WEST, btnAdd);
		add(btnAdd);

		btnRemove = new JButton();
		btnRemove.setIcon(new ImageIcon(getClass().getResource("/images/remove.png")));
		btnRemove.setToolTipText("Remove the selected workflow");
		btnRemove.setEnabled(false);
		btnRemove.setMnemonic(KeyEvent.VK_E);
		btnRemove.setActionCommand("Remove");
		btnRemove.addActionListener(workflowActionListener);
		slWorkflowLayout.putConstraint(SpringLayout.WEST, btnRemove, 30, SpringLayout.EAST, btnAdd);
		slWorkflowLayout.putConstraint(SpringLayout.NORTH, btnRemove, 6, SpringLayout.SOUTH, cbxEnvironments);
		slWorkflowLayout.putConstraint(SpringLayout.EAST, btnRemove, 50, SpringLayout.WEST, btnRemove);
		add(btnRemove);
		
		btnCopy = new JButton();
		btnCopy.setIcon(new ImageIcon(getClass().getResource("/images/copy.png")));
		btnCopy.setToolTipText("Copy the selected workflow");
		btnCopy.setEnabled(false);
		btnCopy.setMnemonic(KeyEvent.VK_C);
		btnCopy.setActionCommand("Copy");
		btnCopy.addActionListener(workflowActionListener);
		slWorkflowLayout.putConstraint(SpringLayout.WEST, btnCopy, 30, SpringLayout.EAST, btnRemove);
		slWorkflowLayout.putConstraint(SpringLayout.NORTH, btnCopy, 6, SpringLayout.SOUTH, cbxEnvironments);
		slWorkflowLayout.putConstraint(SpringLayout.EAST, btnCopy, 50, SpringLayout.WEST, btnCopy);
		add(btnCopy);
		
		btnPaste = new JButton();
		btnPaste.setIcon(new ImageIcon(getClass().getResource("/images/paste.png")));
		btnPaste.setToolTipText("Paste the copied workflow");
		btnPaste.setEnabled(false);
		btnPaste.setMnemonic(KeyEvent.VK_P);
		btnPaste.setActionCommand("Paste");
		btnPaste.addActionListener(workflowActionListener);
		slWorkflowLayout.putConstraint(SpringLayout.WEST, btnPaste, 30, SpringLayout.EAST, btnCopy);
		slWorkflowLayout.putConstraint(SpringLayout.NORTH, btnPaste, 6, SpringLayout.SOUTH, cbxEnvironments);
		slWorkflowLayout.putConstraint(SpringLayout.EAST, btnPaste, 50, SpringLayout.WEST, btnPaste);
		add(btnPaste);
		
		workflows = new DefaultListModel<Workflow>();
		lstWorkflows = new JList<Workflow>(workflows)
		{
			private static final long serialVersionUID = 1L;

			/**
			 * Utilizes the {@link settings.Workflow Workflow}'s description for tooltips.
			 * @param mouseEvent The triggering event for the tooltip.
			 * @returns A tooltip representing the {@link settings.Workflow Workflow}'s description.
			 */
			@Override
			public String getToolTipText(MouseEvent mouseEvent) 
			{
				String toolTip = null;
				
				if (locationToIndex(mouseEvent.getPoint()) != -1)
				{
					toolTip = getModel().getElementAt(locationToIndex(mouseEvent.getPoint())).getDescription();
				}
				
				return toolTip == null || toolTip.isEmpty() ? null : toolTip;
		    }
		};
		lstWorkflows.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
		lstWorkflows.setDragEnabled(true);
		lstWorkflows.setDropMode(DropMode.INSERT);
		lstWorkflows.addListSelectionListener(workflowListSelectionListener);
		lstWorkflows.setTransferHandler(workflowTransferHandler);

		scpWorkflows = new JScrollPane(lstWorkflows);
		slWorkflowLayout.putConstraint(SpringLayout.WEST, scpWorkflows, 10, SpringLayout.WEST, this);
		slWorkflowLayout.putConstraint(SpringLayout.NORTH, scpWorkflows, 0, SpringLayout.SOUTH, btnAdd);
		slWorkflowLayout.putConstraint(SpringLayout.EAST, scpWorkflows, 0, SpringLayout.EAST, btnPaste);
		slWorkflowLayout.putConstraint(SpringLayout.SOUTH, scpWorkflows, -10, SpringLayout.SOUTH, this);
		add(scpWorkflows);
		
		lblName = new JLabel("Name:");
		lblName.setEnabled(false);
		slWorkflowLayout.putConstraint(SpringLayout.WEST, lblName, 6, SpringLayout.EAST, scpWorkflows);
		add(lblName);

		lblIterations = new JLabel("Iterations:");
		lblIterations.setEnabled(false);
		slWorkflowLayout.putConstraint(SpringLayout.WEST, lblIterations, 6, SpringLayout.EAST, scpWorkflows);
		add(lblIterations);

		lblDescription = new JLabel("Description:");
		lblDescription.setEnabled(false);
		slWorkflowLayout.putConstraint(SpringLayout.WEST, lblDescription, 6, SpringLayout.EAST, scpWorkflows);
		slWorkflowLayout.putConstraint(SpringLayout.NORTH, lblDescription, 12, SpringLayout.SOUTH, lblIterations);
		add(lblDescription);

		txtName = new JFormattedTextField();
		txtName.setEnabled(false);
		txtName.addFocusListener(workflowFocusAdapter);
		txtName.addKeyListener(workflowKeyAdapter);
		slWorkflowLayout.putConstraint(SpringLayout.VERTICAL_CENTER, lblName, 0, SpringLayout.VERTICAL_CENTER, txtName);
		slWorkflowLayout.putConstraint(SpringLayout.WEST, txtName, -300, SpringLayout.EAST, this);
		slWorkflowLayout.putConstraint(SpringLayout.NORTH, txtName, 0, SpringLayout.NORTH, scpWorkflows);
		slWorkflowLayout.putConstraint(SpringLayout.EAST, txtName, -10, SpringLayout.EAST, this);
		add(txtName);

		sprIterations = new JSpinner();
		sprIterations.setModel(new SpinnerNumberModel(new Integer(1), new Integer(1), null, new Integer(1)));
		sprIterations.setEnabled(false);
		((JSpinner.DefaultEditor)sprIterations.getEditor()).getTextField().addKeyListener(workflowKeyAdapter);
		sprIterations.addChangeListener(workflowChangeListener);
		slWorkflowLayout.putConstraint(SpringLayout.VERTICAL_CENTER, lblIterations, 0, SpringLayout.VERTICAL_CENTER, sprIterations);
		slWorkflowLayout.putConstraint(SpringLayout.WEST, sprIterations, -300, SpringLayout.EAST, this);
		slWorkflowLayout.putConstraint(SpringLayout.NORTH, sprIterations, 6, SpringLayout.SOUTH, txtName);
		slWorkflowLayout.putConstraint(SpringLayout.EAST, sprIterations, -10, SpringLayout.EAST, this);
		add(sprIterations);

		txtDescription = new JTextPane();
		txtDescription.setEnabled(false);
		txtDescription.addFocusListener(workflowFocusAdapter);
		txtDescription.addKeyListener(workflowKeyAdapter);
		slWorkflowLayout.putConstraint(SpringLayout.WEST, txtDescription, 6, SpringLayout.EAST, scpWorkflows);
		slWorkflowLayout.putConstraint(SpringLayout.NORTH, txtDescription, 6, SpringLayout.SOUTH, lblDescription);
		slWorkflowLayout.putConstraint(SpringLayout.EAST, txtDescription, -10, SpringLayout.EAST, this);
		slWorkflowLayout.putConstraint(SpringLayout.SOUTH, txtDescription, 100, SpringLayout.NORTH, txtDescription);
		add(txtDescription);
		
		btnSave = new JButton("Save");
		btnSave.setMnemonic(KeyEvent.VK_S);
		btnSave.addActionListener(workflowActionListener);
		slWorkflowLayout.putConstraint(SpringLayout.SOUTH, btnSave, -10, SpringLayout.SOUTH, this);
		add(btnSave);
				
		btnRevert = new JButton("Revert");
		btnRevert.setMnemonic(KeyEvent.VK_R);
		btnRevert.addActionListener(workflowActionListener);
		slWorkflowLayout.putConstraint(SpringLayout.EAST, btnSave, -6, SpringLayout.WEST, btnRevert);
		slWorkflowLayout.putConstraint(SpringLayout.EAST, btnRevert, -10, SpringLayout.EAST, this);
		slWorkflowLayout.putConstraint(SpringLayout.SOUTH, btnRevert, -10, SpringLayout.SOUTH, this);
		add(btnRevert);
		
		setEditedState(false);
	}

	/**
	 * Prompts the user to save any relevant changes, if necessary.
	 */
	@Override
	public void promptSaveChanges()
	{
		if (btnSave.isEnabled())
		{
			if (JOptionPane.showConfirmDialog(null,
											  "Save workflow changes for " + currentEnvironment.getName() + "?",
											  "Workflows",
											  JOptionPane.YES_NO_OPTION,
											  JOptionPane.WARNING_MESSAGE) == JOptionPane.YES_OPTION)
			{
				save();
			}
		}
		
		setEditedState(false);
	}
	
	/**
	 * Updates the availability of all Workflow-related controls.
	 */
	@Override
	public void setEnabled(boolean enabled)
	{
		btnRemove.setEnabled(enabled);
		btnCopy.setEnabled(enabled);
		
		lblName.setEnabled(enabled);
		lblIterations.setEnabled(enabled);
		lblDescription.setEnabled(enabled);
		
		txtName.setEnabled(enabled);
		sprIterations.setEnabled(enabled);
		txtDescription.setEnabled(enabled);
	}
	
	/**
	 * Saves all Workflow objects for the current Environment.
	 */
	public void save()
	{
		setEditedState(false);
		
		Settings settings = LocalDataAccess.loadSettings();

		List<Environment> environments = settings.getEnvironments();
		environments.set(environments.indexOf(currentEnvironment), currentEnvironment);
		settings.setEnvironments(environments);

		LocalDataAccess.saveSettings(settings);
		resetFocus();
	}
	
	/**
	 * Loads all saved Environment objects and consequently all saved Workflow objects.
	 */
	@Override
	public void load()
	{
		setEditedState(false);
		
		environments.removeAllElements();
		for (Environment environment : LocalDataAccess.loadSettings().getEnvironments())
		{
			environments.addElement(environment);
		}
		environments.setSelectedItem(null);
		lstWorkflows.setSelectedIndex(-1);
		
		currentEnvironment = null;
		currentWorkflow = null;
		
		copiedWorkflow = null;
		btnPaste.setEnabled(false);
		btnPaste.setToolTipText("Paste the copied workflow");
		
		resetFocus();
	}

	/**
	 * Sets the availability of the Save and Revert buttons based on the given state.
	 * @param edited True if any Workflow objects have been changed; false if they reflect the settings.txt file.
	 */
	private void setEditedState(boolean edited)
	{
		btnSave.setEnabled(edited);
		btnRevert.setEnabled(edited);
	}
	
	/**
	 * Updates the selected Workflow to reflect what the UI currently displays.
	 * If no Workflow is selected, the UI will be updated.
	 */
	private void updateWorkflow()
	{
		if (currentWorkflow == null)
		{
			txtName.setText("");
			sprIterations.setValue(1);
			txtDescription.setText("");
			
			// Ensure that a recently disabled element is not focused.
			resetFocus();
		}
		else if (workflows.contains(currentWorkflow))
		{
			if (!currentWorkflow.getName().equals(txtName.getText()) || 
				currentWorkflow.getIterations() != (Integer) sprIterations.getValue() ||
				!currentWorkflow.getDescription().equals(txtDescription.getText()))
			{
				setEditedState(true);
			}

			// Protect against setting the Workflow name to nothing.
			String name = txtName.getText();
			if (name == null || name.equals(""))
			{
				txtName.setText("New workflow");
			}

			currentWorkflow.setName(txtName.getText());
			currentWorkflow.setIterations((Integer) sprIterations.getValue());
			currentWorkflow.setDescription(txtDescription.getText());

			workflows.set(workflows.indexOf(currentWorkflow), currentWorkflow);

			// DefaultListModel does not support toArray(T[]).
			Workflow[] newWorkflows = new Workflow[workflows.getSize()];
			workflows.copyInto(newWorkflows);
			currentEnvironment.setWorkflows(new ArrayList<Workflow>(Arrays.asList(newWorkflows)));
		}
		
		currentWorkflow = lstWorkflows.getSelectedValue();

		setEnabled(currentWorkflow != null);
		btnAdd.setEnabled(currentEnvironment != null);
		
		if (currentWorkflow != null)
		{
			btnRemove.setToolTipText("Remove the " + currentWorkflow.toString() + " workflow");
			btnCopy.setToolTipText("Copy the " + currentWorkflow.toString() + " workflow");
		}
		else
		{
			btnRemove.setToolTipText("Remove the selected workflow");
			btnCopy.setToolTipText("Copy the selected workflow");
		}
	}

	/**
	 * ActionListener for the Environment JComboBox and Workflow JButton objects.
	 * @author BDS
	 */
	private class WorkflowActionListener implements ActionListener
	{
		/**
		 * Executes specific commands based on the ActionEvent.
		 * Environment - Load the Workflow objects for the selected Environment.
		 * Add - Creates a new Workflow.
		 * Copy - Copies the selected Workflow to the pseudo clipboard.
		 * Paste - Adds the copied Workflow from the pseudo clipboard.
		 * Remove - Removes the selected Workflow.
		 * Save - Saves the Workflow changes for the selected Environment. 
		 * Revert - Reverts the selected Environment Workflow objects to their previously saved sate.
		 */
		@Override
		public void actionPerformed(ActionEvent actionEvent)
		{
			if (actionEvent.getActionCommand().equals(cbxEnvironments.getActionCommand()))
			{
				if (currentEnvironment != cbxEnvironments.getSelectedItem())
				{
					updateWorkflow();
					promptSaveChanges();
					
					currentEnvironment = (Environment) cbxEnvironments.getSelectedItem();
					workflows.clear();
					if (currentEnvironment != null)
					{
						for (Workflow workflow : currentEnvironment.getWorkflows())
						{
							workflows.addElement(workflow);
						}
					}
					lstWorkflows.setSelectedIndex(-1);
					currentWorkflow = null;
					
					updateWorkflow();
				}
			}
			if (actionEvent.getActionCommand().equals(btnAdd.getActionCommand()))
			{
				setEditedState(true);
	
				currentWorkflow = new Workflow();
				
				txtName.setText(currentWorkflow.getName());
				sprIterations.setValue(currentWorkflow.getIterations());
				txtDescription.setText(currentWorkflow.getDescription());
	
				workflows.insertElementAt(currentWorkflow, lstWorkflows.getSelectedIndex() + 1);
				lstWorkflows.setSelectedIndex(lstWorkflows.getSelectedIndex() + 1);
				lstWorkflows.ensureIndexIsVisible(lstWorkflows.getSelectedIndex());
				
				updateWorkflow();
			}
			else if (actionEvent.getActionCommand().equals(btnRemove.getActionCommand()))
			{
				if (currentWorkflow != null)
				{
					setEditedState(true);
					
					int currentIndex = lstWorkflows.getSelectedIndex();
					workflows.removeElement(currentWorkflow);
					
					// DefaultListModel does not support toArray(T[]).
					Workflow[] newWorkflows = new Workflow[workflows.getSize()];
					workflows.copyInto(newWorkflows);
					currentEnvironment.setWorkflows(new ArrayList<Workflow>(Arrays.asList(newWorkflows)));
					
					currentWorkflow = null;
					updateWorkflow();
					lstWorkflows.setSelectedIndex(currentIndex);
				}
			}
			else if (actionEvent.getActionCommand().equals(btnCopy.getActionCommand()))
			{
				copiedWorkflow = new Workflow(currentWorkflow);
				copyNumber = 1;
				
				btnPaste.setEnabled(true);
				btnPaste.setToolTipText("Paste the " + currentWorkflow.toString() + " workflow");
			}
			else if (actionEvent.getActionCommand().equals(btnPaste.getActionCommand()))
			{
				setEditedState(true);
	
				Workflow pastedWorkflow = new Workflow(copiedWorkflow);
				pastedWorkflow.setName(pastedWorkflow.getName() + " - Copy " + copyNumber);
				copyNumber++;

				workflows.insertElementAt(pastedWorkflow, lstWorkflows.getSelectedIndex() + 1);	
				lstWorkflows.setSelectedIndex(lstWorkflows.getSelectedIndex() + 1);
				lstWorkflows.ensureIndexIsVisible(lstWorkflows.getSelectedIndex());
				
				updateWorkflow();
			}
			else if (actionEvent.getActionCommand().equals(btnSave.getActionCommand()))
			{
				save();
			}
			else if (actionEvent.getActionCommand().equals(btnRevert.getActionCommand()))
			{
				load();
			}
		}
	}

	/**
	 * ListSelectionListener for the Workflow JList.
	 * @author BDS
	 */
	private class WorkflowListSelectionListener implements ListSelectionListener
	{
		/**
		 * Updates the UI with the selected Workflow.
		 */
		@Override
		public void valueChanged(ListSelectionEvent listSelectionEvent)
		{
			updateWorkflow();
			
			if (currentWorkflow != null)
			{
				txtName.setText(currentWorkflow.getName());
				sprIterations.setValue(currentWorkflow.getIterations());
				txtDescription.setText(currentWorkflow.getDescription());
			}
		}
	}

	/**
	 * TransferHandler for the Workflow JList.
	 * @author BDS
	 */
	private class WorkflowTransferHandler extends TransferHandler
	{
		private static final long serialVersionUID = 1L;

		/**
		 * Supports any drop location.
		 */
		public boolean canImport(TransferSupport transferSupport)
		{
			return true;
		}
		
		/**
		 * Captures the dragged Workflow.
		 */
	    @SuppressWarnings("unchecked")
		protected Transferable createTransferable(JComponent component) 
	    {	        
	    	return ((JList<Workflow>) component).getSelectedValue();
	    }
	    
	    /**
	     *  Supports only moving Workflow objects.
	     */
	    public int getSourceActions(JComponent component) {
	        return TransferHandler.MOVE;
	    }
		
	    /**
	     * Moves the dragged Workflow.
	     */
		@SuppressWarnings("unchecked")
		public boolean importData(TransferSupport transferSupport)
		{
			try
			{
				setEditedState(true);
				
				Workflow dragWorkflow = (Workflow) transferSupport.getTransferable().getTransferData(DataFlavor.stringFlavor);
				
				int previousIndex = ((JList<Workflow>) transferSupport.getComponent()).getSelectedIndex();
				int newIndex = ((JList.DropLocation) transferSupport.getDropLocation()).getIndex();
				if (newIndex > previousIndex)
				{
					newIndex--;
				}

				((DefaultListModel<Workflow>) ((JList<Workflow>) transferSupport.getComponent()).getModel()).removeElement(dragWorkflow);
				((DefaultListModel<Workflow>) ((JList<Workflow>) transferSupport.getComponent()).getModel()).insertElementAt(dragWorkflow, newIndex);
				
				((JList<Workflow>) transferSupport.getComponent()).setSelectedIndex(newIndex);
				((JList<Workflow>) transferSupport.getComponent()).ensureIndexIsVisible(((JList<Workflow>) transferSupport.getComponent()).getSelectedIndex());
				
				updateWorkflow();
			}
			catch (Exception exception)
			{
				Logger.logException(exception);
			}
			
			return true;
		}
	}
	
	/**
	 * FocusAdapter for the Workflow JFormattedTextField objects.
	 * @author BDS
	 */
	private class WorkflowFocusAdapter extends FocusAdapter
	{
		/**
		 * Updates the selected Workflow.
		 */
		@Override
		public void focusLost(FocusEvent focusEvent)
		{
			updateWorkflow();
		}
	}

	/**
	 * KeyAdapter for the Workflow JSpinner and JFormattedTextField objects.
	 * @author BDS
	 */
	private class WorkflowKeyAdapter extends KeyAdapter
	{
		/**
		 * Clears focus when the Enter key is pressed, which will update the selected Workflow.
		 */
		@Override
		public void keyTyped(KeyEvent keyEvent)
		{
			if (keyEvent.getKeyChar() == '\n')
			{
				resetFocus();
			}
			
			setEditedState(true);
		}
	}

	/**
	 * ChangeListener for the Workflow JSpinner.
	 * @author BDS
	 */
	private class WorkflowChangeListener implements ChangeListener
	{
		@Override
		public void stateChanged(ChangeEvent changeEvent)
		{
			updateWorkflow();
		}
	}
}
